<?php

class WebUser
{
    private $isAuthenticated = false;

    protected $states = null;
    protected $keyPrefix = '';
    protected $isRememberMeEnabled = false;

    const USERNAME_KEY = 'username';

    const REMEMBER_ME_PERIOD = 2592000; // one month in sec

    /**
     * Index of random key value in cookie data
     */
    const KEY_INDEX = 0;

    /**
     * Index of username value in cookie data
     */
    const USERNAME_INDEX = 1;

    /**
     * "Remember Me" key size in bytes
     */
    const RANDOM_KEY_SIZE = 16;


    public function __construct()
    {
        $this->keyPrefix = 'iSpring.' . get_class($this) . '.';

        $this->Initialize();
    }

    public function Initialize()
    {
        if ( $this->AutoLogin() )
        {
            return;
        }

        $username = Session::GetValue($this->keyPrefix . self::USERNAME_KEY);
        if ( $username )
        {
            $this->SetAuthenticated(true);
        }
        else if ( $this->isRememberMeEnabled )
        {
            $this->RestoreFromCookie();
        }
        $this->LoadStates();
    }

    public function IsAuthenticated()
    {
        return $this->isAuthenticated;
    }

    public function SetAuthenticated($authenticated)
    {
        $this->isAuthenticated = $authenticated;
    }

    public function GetUsername()
    {
        return $this->GetState(self::USERNAME_KEY, '');
    }

    public function Login( $username, $remember = false )
    {
        $this->SetState(self::USERNAME_KEY, $username);

        $this->SetAuthenticated(true);

        if ( $remember && $this->isRememberMeEnabled )
        {
            $this->Remember($username);
        }
    }

    public function Logout()
    {
        if ($this->isRememberMeEnabled)
        {
            $this->RemoveRememberMeCookie();
        }
        $this->ClearStates();
        $this->SetAuthenticated(false);
    }

    public function GetLoginUrl()
    {
        return '';
    }

    public function GetReturnUrl($defaultUrl = null)
    {
        return $this->GetState('returnUrl', $defaultUrl);
    }

    public function SetReturnUrl($returnUrl)
    {
        $this->SetState('returnUrl', $returnUrl);
    }

    public function GetState($key, $defaultValue = null, $clear = false)
    {
        $state = $defaultValue;
        
        if ( isset( $this->states[$key] ) )
        {
            $state = $this->states[$key];
            
            if ( $clear )
            {
                $this->ClearState($key);
            }
        }
        
        return $state;
    }

    public function SetState($key, $value)
    {
        $this->states[$key] = $value;
        Session::SetValue($this->keyPrefix . $key, $value);
    }

    public function ClearState($key)
    {
        if ( isset($this->states[$key]) )
        {
            unset($this->states[$key]);
        }

        Session::RemoveKey($this->keyPrefix . $key);
    }

    public function ClearStates()
    {
        $keys = Session::GetKeys();
        $prefixLen = strlen($this->keyPrefix);
        foreach ($keys as $key)
        {
            if ( strncmp($key, $this->keyPrefix, $prefixLen) === 0)
            {
                Session::RemoveKey($key);
            }
        }

        $this->states = null;
        $this->states = array();
    }

    public function GetDisplayName()
    {
        return $this->GetUsername();
    }

    public function PrepareLogin()
    {
    }

    /**
     * Override to implement autologin logic
     * @return bool True if user is auto logged in, false otherwise
     */
    public function AutoLogin()
    {
        return false;
    }

    protected function LoadStates()
    {
        $keys = Session::GetKeys();
        $prefixLen = strlen($this->keyPrefix);
        foreach ($keys as $key)
        {
            if ( strncmp($key, $this->keyPrefix, $prefixLen) === 0)
            {
                $stateKey = substr($key, $prefixLen);
                $this->states[$stateKey] = Session::GetValue($key);
            }
        }
    }

    protected function Remember( $username )
    {
        $key = $this->GenerateRandomKey();

        $this->SaveToDB($username, $key, self::REMEMBER_ME_PERIOD);

        $cookieValue = base64_encode( serialize(array($key, $username)) );
        $cookie = new Cookie($this->GetRememberMeCookieName(), time() + self::REMEMBER_ME_PERIOD);
        $cookie->SetValue($cookieValue);
        $cookie->Save();
    }

    protected function RestoreFromCookie()
    {
        $cookie = new Cookie($this->GetRememberMeCookieName());
        if ($cookie->IsPresent())
        {
            $value = unserialize( base64_decode($cookie->GetValue()) );

            if ( !empty($value[self::USERNAME_INDEX]) && !empty($value[self::KEY_INDEX]) )
            {
                $username = $value[self::USERNAME_INDEX];
                $session = $this->GetSessionFromDB( $username, $value[self::KEY_INDEX] );
                if ($session)
                {
                    $this->Login($username, false);
                }
            }
        }
    }

    protected function SaveToDB( $username, $key, $duration )
    {
        try
        {
            $query = 'INSERT INTO session (username, `key`, time, expiry_time, ip_address)
                      VALUE (@username, @key, @time, @expiry_time, INET_ATON(@ip_address))';

            $time = gmmktime();

            Core::DBConn()->Query($query, Util::QuoteArray(array(
                '@username'    => $username,
                '@key'         => $key,
                '@time'        => $time,
                '@expiry_time' => $time + $duration,
                '@ip_address'  => Request::GetRemoteAddress()
            )));
        }
        catch (Exception $ex)
        {
            Logger::LogException(APP_LOG_FILE, $ex);
        }
    }

    protected function GetSessionFromDB( $username, $key )
    {
        $session = null;
        try
        {
            $query = 'SELECT * FROM session WHERE username = @username AND `key` = @key ORDER BY time DESC LIMIT 1';
            $session = Core::DBConn()->Query($query, array(
                '@username' => $username,
                '@key'      => $key
            ))->GetAssoc();
        }
        catch (Exception $ex)
        {
            Logger::LogException(APP_LOG_FILE, $ex);
        }

        return $session;
    }

    protected function RemoveRememberMeCookie()
    {
        $cookie = new Cookie($this->GetRememberMeCookieName(), 0);
        $cookie->SetValue(null);
        $cookie->Save();
    }

    protected function GetRememberMeCookieName()
    {
        return md5($this->keyPrefix);
    }

    private function GenerateRandomKey()
    {
        return KeyGenerator::generateSolidKey(self::RANDOM_KEY_SIZE);
    }
}

?>
